/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
**     the names of its contributors may be used to endorse or promote
**     products derived from this software without specific prior written
**     permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
** $QT_END_LICENSE$
**
****************************************************************************/

/****************************************************************************
** LICENSE:BSD
** Author: Alejandro Castaño del Castillo
** Author: Francisco Javier Martin Otero
** Author: Fernando Rosario Costa
****************************************************************************/

#define N_ANGLES 12

#define DEFAULT_DISTANCE_WEIGHT 0.5
#define DEFAULT_MINIMUM_FACTOR 0.0

#define DEFAULT_BLUR_SIZE 5
#define DEFAULT_MAXIMUM_GAUSS 11
#define DEFAULT_MINIMUM_GAUSS 3
#define DEFAULT_STEP_GAUSS 2


#include <QtGui>

#include "mainwindow.h"

using namespace cv;

MainWindow::MainWindow()
{
    // Set pixmap properties
    imageLabel = new QLabel;
    imageLabel->setBackgroundRole(QPalette::Base);
    imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    imageLabel->setScaledContents(true);

    // Pixmap is in the scroll area
    scrollArea = new QScrollArea;
    scrollArea->setBackgroundRole(QPalette::Dark);
    scrollArea->setWidget(imageLabel);
    // And the scroll area is the center of the window
    setCentralWidget(scrollArea);

    loadDescriptions();

    // Add a slider
    slider = new QSlider(Qt::Horizontal);
    slider->setEnabled(false);
    slider->setMinimum(0);
    slider->setMaximum(descriptions.size() - 1);
    // When the slider changes, description and image change too
    connect(slider, SIGNAL(valueChanged(int)), this, SLOT(slotValueChanged(int)));

    // Slider is on the top of the window
    topDock = new QDockWidget(tr("Steps"));
    topDock->setWidget(slider);
    topDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
    addDockWidget(Qt::TopDockWidgetArea, topDock);

    // Add a text where descriptions are written
    text = new QTextEdit();
    text->setReadOnly(true);
    text->setEnabled(false);

    // Text area in the bottom of the window
    downDock =  new QDockWidget(tr("Description"));
    downDock->setWidget(text);
    downDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
    addDockWidget(Qt::BottomDockWidgetArea, downDock);

    // Right part
    QVBoxLayout* rightLayout = new QVBoxLayout();


    QLabel* label = new QLabel();
    label->setFrameStyle(QFrame::HLine);
    label->setLineWidth(2);
    rightLayout->addWidget(label);

    label = new QLabel();
    label->setText(tr("First threshold"));
    rightLayout->addWidget(label);
    inputFirstThreshold = new QSpinBox();
    inputFirstThreshold->setMinimum(0);
    inputFirstThreshold->setMaximum(256);
    inputFirstThreshold->setValue(230);
    rightLayout->addWidget(inputFirstThreshold);

    label = new QLabel();
    label->setFrameStyle(QFrame::HLine);
    label->setLineWidth(2);
    rightLayout->addWidget(label);

    label = new QLabel();
    label->setText(tr("Matrix Blur size"));
    rightLayout->addWidget(label);
    inputBlurSize = new QSpinBox();
    inputBlurSize->setValue(DEFAULT_BLUR_SIZE);
    inputBlurSize->setMinimum(DEFAULT_MINIMUM_GAUSS);
    inputBlurSize->setMaximum(DEFAULT_MAXIMUM_GAUSS);
    inputBlurSize->setSingleStep(DEFAULT_STEP_GAUSS);
    connect(inputBlurSize, SIGNAL(valueChanged(int)), this, SLOT(slotIsOdd(int)));
    rightLayout->addWidget(inputBlurSize);

    label = new QLabel();
    label->setText(tr("Minimum Factor"));
    rightLayout->addWidget(label);
    inputMinimumFactor = new QDoubleSpinBox();
    inputMinimumFactor->setValue(DEFAULT_MINIMUM_FACTOR);
    inputMinimumFactor->setMinimum(0.0);
    inputMinimumFactor->setMaximum(1.0);
    inputMinimumFactor->setSingleStep(0.05);
    rightLayout->addWidget(inputMinimumFactor);

    label = new QLabel();
    label->setText(tr("Distance Weight"));
    rightLayout->addWidget(label);
    inputDistanceWeight = new QDoubleSpinBox();
    inputDistanceWeight->setValue(DEFAULT_DISTANCE_WEIGHT);
    inputDistanceWeight->setMinimum(0.0);
    inputDistanceWeight->setMaximum(1.0);
    inputDistanceWeight->setSingleStep(0.05);
    rightLayout->addWidget(inputDistanceWeight);

    label = new QLabel();
    label->setText(tr("Iterations"));
    rightLayout->addWidget(label);
    inputNIterations = new QSpinBox();
    inputNIterations->setValue(5);
    inputNIterations->setMinimum(0);
    inputNIterations->setMaximum(100);
    rightLayout->addWidget(inputNIterations);

    label = new QLabel();
    label->setFrameStyle(QFrame::HLine);
    label->setLineWidth(2);
    rightLayout->addWidget(label);

    label = new QLabel();
    label->setText(tr("Second threshold"));
    rightLayout->addWidget(label);
    inputSecondThreshold = new QSpinBox();
    inputSecondThreshold->setMinimum(0);
    inputSecondThreshold->setMaximum(256);
    inputSecondThreshold->setValue(240);
    rightLayout->addWidget(inputSecondThreshold);
    rightLayout->addStretch();

    QPushButton *button = new QPushButton("&Apply changes");
    rightLayout->addWidget(button);

    QWidget* auxWidget = new QWidget();
    auxWidget->setLayout(rightLayout);

    // Right dock
    rightDock = new QDockWidget(tr("Input Values"));
    rightDock->setWidget(auxWidget);
    rightDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
    addDockWidget(Qt::RightDockWidgetArea, rightDock);

    connect(button, SIGNAL(clicked()), this, SLOT(slotApplyChanges()));

    createActions();
    createMenus();

    setWindowTitle(tr("MotionBlur"));
    //resize(500, 400);
}

void MainWindow::open()
{
    // Dialog to select the file
    QString fileName = QFileDialog::getOpenFileName(this,
                                    tr("Open File"), QDir::currentPath());
    // If a file was select
    if (!fileName.isEmpty())
    {
        imageName = fileName.toUtf8().constData();
        // But the file wasn't load correctly as image
        if (!loadImages())
        {
            QMessageBox::information(this, tr("Image Viewer"),
                                     tr("Cannot load %1.").arg(fileName));
            return;
        }

        // If not
        // the slider and the text are enabled
        slider->setEnabled(true);
        text->setEnabled(true);

        // And set the slider to 0
        slider->setValue(0);
        slotValueChanged(0);

        scaleFactor = 1.0;

        printAct->setEnabled(true);
        fitToWindowAct->setEnabled(true);
        updateActions();

        if (!fitToWindowAct->isChecked())
            imageLabel->adjustSize();
    }
}

void MainWindow::print()
{
    Q_ASSERT(imageLabel->pixmap());
#ifndef QT_NO_PRINTER
    QPrintDialog dialog(&printer, this);
    if (dialog.exec()) {
        QPainter painter(&printer);
        QRect rect = painter.viewport();
        QSize size = imageLabel->pixmap()->size();
        size.scale(rect.size(), Qt::KeepAspectRatio);
        painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
        painter.setWindow(imageLabel->pixmap()->rect());
        painter.drawPixmap(0, 0, *imageLabel->pixmap());
    }
#endif
}

void MainWindow::zoomIn()
{
    scaleImage(1.25);
}

void MainWindow::zoomOut()
{
    scaleImage(0.8);
}

void MainWindow::normalSize()
{
    imageLabel->adjustSize();
    scaleFactor = 1.0;
}

void MainWindow::fitToWindow()
{
    bool fitToWindow = fitToWindowAct->isChecked();
    scrollArea->setWidgetResizable(fitToWindow);
    if (!fitToWindow) {
        normalSize();
    }
    updateActions();
}

void MainWindow::about()
{
    QMessageBox::about(this, tr("About Image Viewer"),
            tr("<p>This program detects creases in fingerprint images.</p>"));
}

void MainWindow::slotValueChanged(int value)
{
    text->setText(descriptions[value]);
    imageLabel->setPixmap(images[value]);
}

void MainWindow::createActions()
{
    openAct = new QAction(tr("&Open..."), this);
    openAct->setShortcut(tr("Ctrl+O"));
    connect(openAct, SIGNAL(triggered()), this, SLOT(open()));

    printAct = new QAction(tr("&Print..."), this);
    printAct->setShortcut(tr("Ctrl+P"));
    printAct->setEnabled(false);
    connect(printAct, SIGNAL(triggered()), this, SLOT(print()));

    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcut(tr("Ctrl+Q"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));

    zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
    zoomInAct->setShortcut(tr("Ctrl++"));
    zoomInAct->setEnabled(false);
    connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));

    zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
    zoomOutAct->setShortcut(tr("Ctrl+-"));
    zoomOutAct->setEnabled(false);
    connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));

    normalSizeAct = new QAction(tr("&Normal Size"), this);
    normalSizeAct->setShortcut(tr("Ctrl+S"));
    normalSizeAct->setEnabled(false);
    connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));

    fitToWindowAct = new QAction(tr("&Fit to Window"), this);
    fitToWindowAct->setEnabled(false);
    fitToWindowAct->setCheckable(true);
    fitToWindowAct->setShortcut(tr("Ctrl+F"));
    connect(fitToWindowAct, SIGNAL(triggered()), this, SLOT(fitToWindow()));

    aboutAct = new QAction(tr("&About"), this);
    connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

    aboutQtAct = new QAction(tr("About &Qt"), this);
    connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}

void MainWindow::createMenus()
{
    fileMenu = new QMenu(tr("&File"), this);
    fileMenu->addAction(openAct);
    fileMenu->addAction(printAct);
    fileMenu->addSeparator();
    fileMenu->addAction(exitAct);

    viewMenu = new QMenu(tr("&View"), this);
    viewMenu->addAction(zoomInAct);
    viewMenu->addAction(zoomOutAct);
    viewMenu->addAction(normalSizeAct);
    viewMenu->addSeparator();
    viewMenu->addAction(fitToWindowAct);

    helpMenu = new QMenu(tr("&Help"), this);
    helpMenu->addAction(aboutAct);
    helpMenu->addAction(aboutQtAct);

    menuBar()->addMenu(fileMenu);
    menuBar()->addMenu(viewMenu);
    menuBar()->addMenu(helpMenu);
}

void MainWindow::updateActions()
{
    zoomInAct->setEnabled(!fitToWindowAct->isChecked());
    zoomOutAct->setEnabled(!fitToWindowAct->isChecked());
    normalSizeAct->setEnabled(!fitToWindowAct->isChecked());
}

void MainWindow::scaleImage(double factor)
{
    Q_ASSERT(imageLabel->pixmap());
    scaleFactor *= factor;
    imageLabel->resize(scaleFactor * imageLabel->pixmap()->size());

    adjustScrollBar(scrollArea->horizontalScrollBar(), factor);
    adjustScrollBar(scrollArea->verticalScrollBar(), factor);

    zoomInAct->setEnabled(scaleFactor < 3.0);
    zoomOutAct->setEnabled(scaleFactor > 0.333);
}

void MainWindow::adjustScrollBar(QScrollBar *scrollBar, double factor)
{
    scrollBar->setValue(int(factor * scrollBar->value()
                            + ((factor - 1) * scrollBar->pageStep()/2)));
}

void MainWindow::loadDescriptions()
{
    descriptions.append("Original image.");
    descriptions.append("First binarization.");
    int i = 0;
    for (; i < N_ANGLES; i++)
    {
        descriptions.append("Applying blur motion matrix with direction " + QString::number(i) + "* PI / 12");
    }

    for (i = 0; i < N_ANGLES; i++)
    {
        descriptions.append("Mask with direction " + QString::number(i) + "* PI / 12");
    }
    descriptions.append("Adding all mask.");
    descriptions.append("Mask + First Binarization.");
    descriptions.append("Original again.");
}

void MainWindow::slotApplyChanges()
{
    loadImages();
    slotValueChanged(slider->value());
}

bool MainWindow::loadImages()
{
    if (imageName.isEmpty())
        return false;

    Mat orig = imread(imageName.toUtf8().constData(), 0);
    if (!orig.data)
        return false;

    images.clear();

    // First. the original image
    images.append(fromMatToQPixmap(orig));

    // The first binarization
    Mat firstBinarization(orig.rows, orig.cols, orig.type());
    threshold(orig, firstBinarization, inputFirstThreshold->value(), 254, THRESH_BINARY);
    images.append(fromMatToQPixmap(firstBinarization));

    Mat mats[N_ANGLES];

    int i;
    Mat filter(inputBlurSize->value(), inputBlurSize->value(), CV_32F);

    for (i = 0; i < N_ANGLES; i++)
    {
        mats[i] = Mat(orig.rows, orig.cols, orig.type());
        MotionBlurFilter(filter, CV_PI*i/N_ANGLES);

        filter2D(orig, mats[i], orig.depth(), filter, Point(-1,-1), 0);
        for(int iterations = 0; iterations < inputNIterations->value(); iterations++)
            filter2D(mats[i], mats[i], mats[i].depth(), filter, Point(-1,-1), 0);

        // Adding masks
        images.append(fromMatToQPixmap(mats[i]));
    }

    Mat mask(orig.rows, orig.cols, orig.type(),Scalar(0));
    for (i = 0; i < N_ANGLES; i++)
    {
        // Binarization
        threshold(mats[i], mats[i], inputSecondThreshold->value(), 254, THRESH_BINARY);
        images.append(fromMatToQPixmap(mats[i]));
        mask = mask + mats[i];
    }

    // Adding complete mask
    images.append(fromMatToQPixmap(mask));

    // Mask with orignal image
    mask = mask + firstBinarization;

    QImage qimage((unsigned char *)mask.datastart, mask.cols, mask.rows, mask.step, QImage::Format_Indexed8);
    for(int i = 0 ; i < 255 ; i++)
        qimage.setColor(i,qRgb(i,i,i));
    qimage.setColor(255, qRgb(255,0,0));
    images.append(QPixmap::fromImage(qimage));

    // Adding orig again
    images.append(fromMatToQPixmap(orig));

    return true;
}

const QPixmap MainWindow::fromMatToQPixmap(const Mat& m)
{
    QImage qimage((unsigned char *)m.datastart, m.cols, m.rows, m.step, QImage::Format_Indexed8);
    for(int i = 0 ; i < 256 ; i++)
        qimage.setColor(i,qRgb(i,i,i));

    return QPixmap::fromImage(qimage);
}

void MainWindow::MotionBlurFilter(Mat& blur, REAL angle)
{
    int size = blur.rows;
    int half = int(size / 2.0 - 0.5);
    REAL minimumFactor = inputMinimumFactor->value();

    REAL sum = 0;
    qDebug() << "SuperAngle " << angle;
    for (int y = -half; y <= half; y++)
    {
        for (int x = -half; x <= half; x++)
        {
            REAL newAngle = atan2(y, x);
            if (newAngle < 0)
                newAngle = CV_PI+newAngle;

            REAL angleDifference1 = newAngle - angle;
            if (angleDifference1 < 0)
                angleDifference1 = -angleDifference1;

            REAL angleDifference2 = CV_PI - angleDifference1;
            if (angleDifference2 < 0)
                angleDifference2 = -angleDifference2;

            REAL angleDifference = 0.0;

            if (angleDifference1 > angleDifference2)
                angleDifference = angleDifference2;

            else
                angleDifference = angleDifference1;

            REAL halfpi = CV_PI/2.0;
            REAL angleFactor = (halfpi - angleDifference) / halfpi;

            // change in scale of (0 - 1) to (MINIMUM - 1)
            angleFactor = (angleFactor + minimumFactor) / (1 + minimumFactor);

            REAL distance = x*x + y*y;
            REAL maxDistance = 2*half*half;
            REAL distanceFactor = (maxDistance - distance)/maxDistance;

            // change in scale of (0 - 1) to (MINIMUM - 1)
            distanceFactor = (distanceFactor + minimumFactor) / (1 + minimumFactor);

            REAL value = distanceFactor * inputDistanceWeight->value() +
                           angleFactor * (1 - inputDistanceWeight->value());

            blur.at<REAL>(x + half,-y + half) = value;
            sum += value;
            }
    }

    for (int i = 0; i < blur.rows; i++)
        for (int j = 0; j < blur.rows; j++)
            blur.at<REAL>(i,j) = blur.at<REAL>(i,j) / sum;
}

void MainWindow::slotIsOdd(int value)
{
    if (value % 2 == 0)
    {
        QSpinBox* spinbox = qobject_cast<QSpinBox *>(sender());
        if (spinbox)
        {
            spinbox->setValue(DEFAULT_BLUR_SIZE);
        }
  }
}
